package com.me.mygdxgame.planet;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;

import client.GameClient;

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Screen;
import com.badlogic.gdx.graphics.GL11;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.TextureAtlas;
import com.badlogic.gdx.scenes.scene2d.Stage;
import com.badlogic.gdx.scenes.scene2d.ui.Skin;
import com.me.mygdxgame.chatbox.ChatBox;
import com.me.mygdxgame.player.Player;
import com.me.mygdxgame.player.PlayerGroup;

public class Planet implements Screen {
	
	private static final float MIN_SECONDS_BETWEEN_ARREARENCES = 20f;

	private GameClient gameClient;
	
	private List<Building> buildings;
	private List<PlayerGroup> playerGroups;
	private PlayerGroup myGroup;
	
	private List<PlayerGroup> onScreenGroups;
	private List<Player> onScreenPlayers;
	private PlanetParallaxLayer foreground;
	
	private Random generator;
	private float timeSinceLastEntrance = 0f;
	private Stage stage;
	private ChatBox cBox;
	
	public Planet(PlayerGroup pg, GameClient gameClient) {
		this.gameClient = gameClient;
		
		//TODO
		//this.buildings = ???
		
		this.playerGroups = new ArrayList<PlayerGroup>();
		this.onScreenGroups = new ArrayList<PlayerGroup>();
		this.onScreenPlayers = new ArrayList<Player>();
		
		this.stage = new Stage();
		Gdx.input.setInputProcessor(stage);
		this.addPlayersToStage(pg);
		TextureAtlas textureAtlas = new TextureAtlas(
				Gdx.files.internal("data/uiskin.atlas"));
		Skin fieldSkin = new Skin(Gdx.files.internal("data/uiskin.json"),
				textureAtlas);
		this.cBox = new ChatBox(fieldSkin,this.gameClient);
		this.stage.addActor(cBox);
		
		this.foreground = new PlanetParallaxLayer(
				new Texture(Gdx.files.internal("data/textures/testForeground.png")),
				0f, 0f, 1.05f);
		
		// Initialize myGroup
		this.myGroup = pg;
		this.myGroup.setPosition(50f, 50f);
		this.myGroup.setXVelocity(50f);
		this.initOnScreenGroup(this.myGroup);
		
		// This speeds up initial placement of other players
		this.timeSinceLastEntrance = 40f;

		this.generator = new Random();
	}
	
	public void addGroup(PlayerGroup pg) {
		if (!pg.equals(this.myGroup)) {
			boolean knowsOfPlayerGroup = false;
			if (this.playerGroups.size() > 0) {
				for (PlayerGroup grp : this.playerGroups) {
					if (grp.equals(pg)) {
						knowsOfPlayerGroup = true;
						break;
					}
				}
			}
			// Make sure the client doesn't already know of the group
			if (!knowsOfPlayerGroup) {
				this.initGroup(pg);
			}
		}
	}
	
	/**
	 * Properly adds new groups to the planet
	 * @param pg
	 */
	private void initGroup(PlayerGroup pg) {
		this.playerGroups.add(pg);
	}
	
	/**
	 * Properly set a PlayerGroup's on-screen settings
	 * @param pg
	 */
	private void initOnScreenGroup(PlayerGroup pg) {
		this.onScreenGroups.add(pg);
		this.addPlayersToStage(pg);
		for (Player p : pg.getPlayers()) {
			this.onScreenPlayers.add(p);
		}
		this.sortOnScreenPlayers();
	}
	
	public void addPlayersToStage(PlayerGroup pg) {	
		for (Player p : pg.getPlayers()) {
			p.initPlayer();
			this.stage.addActor(p);
		}
	}
	
	public void removePlayersFromStage(PlayerGroup pg) {
		for (Player p : pg.getPlayers()) {
			p.remove();
		}
	}
	
	/**
	 * Properly removes a PlayerGroup's on-screen settings
	 * @param pg
	 */
	private void releaseOnScreenGroup(PlayerGroup pg) {
		this.onScreenGroups.remove(pg);
		this.removePlayersFromStage(pg);
		for (Player p : pg.getPlayers()) {
			this.onScreenPlayers.remove(p);
		}
		this.sortOnScreenPlayers();
	}
	
	/**
	 * Sorts the currently on-screen players, using Insertion Sort, since the
	 * list will normally be very close if not already sorted, allowing O(n).
	 */
	private void sortOnScreenPlayers() {
		if (this.onScreenPlayers.size() > 1) {
			Player tmp = null;
			for (int i = 1; i < this.onScreenPlayers.size(); i++) {
				for (int c = i; c > 0; c--) {
					// Swap position if necessary
					if (this.onScreenPlayers.get(c).getY() >
							this.onScreenPlayers.get(c - 1).getY()) {
						tmp = this.onScreenPlayers.get(c - 1);
						this.onScreenPlayers.set(c - 1,
								this.onScreenPlayers.get(c));
						this.onScreenPlayers.set(c, tmp);
					} else {
						break; // Sorted index c
					}
				}
			}
		}
	}
	
	public void removeGroup(PlayerGroup pg) {
		if (pg.equals(this.myGroup)) {
			//TODO handle self leaving uniquely
		}
		else {
			if (this.playerGroups.size() > 0) {
				for (PlayerGroup grp : this.playerGroups) {
					if (grp.equals(pg)) {
						this.playerGroups.remove(grp);
						break;
					}
				}
				//TODO Change this to a special animation or something
				for (PlayerGroup grp : this.onScreenGroups) {
					if (grp.equals(pg)) {
						this.releaseOnScreenGroup(grp);
						break;
					}
				}
			}
		}
	}
	
	private void calculatePlayersOnScreen(float delta) {
		this.timeSinceLastEntrance += delta;
		
		// Adds new PlayerGroups to the screen
		if (this.timeSinceLastEntrance > MIN_SECONDS_BETWEEN_ARREARENCES &&
				!this.removeOffscreenGroups() && this.playerGroups.size() > 0 &&
				this.onScreenPlayers.size() < 10) {
			int randIndex = generator.nextInt(this.playerGroups.size());
				
			if (!this.onScreenGroups.contains(
					this.playerGroups.get(randIndex))) {
				// Must add early to prevent asynchronous double adding
				this.initOnScreenGroup(this.playerGroups.get(randIndex));
				//TODO Refine calculations
				float startingY = this.generator.nextFloat() * 100f + 55f;
				int rand = this.generator.nextInt(3);
				if (rand == 0) {
					this.playerGroups.get(randIndex).setPosition(Gdx.graphics.getWidth(), startingY);
					this.playerGroups.get(randIndex).setXVelocity(30f);
				}
				else if (rand == 1) {
					this.playerGroups.get(randIndex).setPosition(Gdx.graphics.getWidth(), startingY);
					this.playerGroups.get(randIndex).setXVelocity(0f);
				}
				else {
					this.playerGroups.get(randIndex).setPosition(-150f, startingY);
					this.playerGroups.get(randIndex).setXVelocity(70f);
				}
					
				this.timeSinceLastEntrance -= MIN_SECONDS_BETWEEN_ARREARENCES;
				// Resort based on the new values set
				this.sortOnScreenPlayers();
			}
		}
	}
	
	/**
	 * Removing must be called in a different render loop to avoid asynchronus
	 * list modification
	 * @return true when a group was removed
	 */
	private boolean removeOffscreenGroups() {
		for (PlayerGroup pg : this.onScreenGroups) {
			if (this.myGroup != pg && (pg.getCurrentX() < -200f ||
					pg.getCurrentX() > (Gdx.graphics.getWidth() + 10f))) {
				this.releaseOnScreenGroup(pg);
				return true;
			}
		}
		return false;
	}

	@Override
	public void render(float delta) {
		Gdx.gl.glClear(GL11.GL_COLOR_BUFFER_BIT);
		// Render the on-screen players
		for (Player p : this.onScreenPlayers)
			p.render(delta, this.myGroup.getXVelocity());
		
		this.foreground.render(delta, this.myGroup.getXVelocity());
		
		stage.act(delta);
		stage.draw();
		
		this.calculatePlayersOnScreen(delta);
	}

	@Override
	public void resize(int width, int height) {
		this.stage.setViewport(width, height, true);
	}

	@Override
	public void show() {}

	@Override
	public void hide() {}

	@Override
	public void pause() {}

	@Override
	public void resume() {}

	@Override
	public void dispose() {
		for (PlayerGroup pg : this.playerGroups) {
			pg.dispose();
		}
		this.stage.dispose();
	}
	
	public ChatBox getChatBox() {
		return this.cBox;
	}
}
